﻿/// <reference path="_namespace.js" />
/// <reference path="../../../drawing/RenderStateManager.js"/>
/// <reference path="../../../components/Component.js"/>
/// <reference path="../../../DomElements.js"/>
/// <reference path="../../../events/EventRouter.js"/>
/// <reference path="../../../drawing/IGraphics.js"/>
/// <reference path="../../../drawing/Graphics.js"/>
/// <reference path="../../../drawing/GraphicsCanvas.js"/>
/// <reference path="../../../math/Matrix.js" />
/// <reference path="../../../math/Vector3.js" />
/// <reference path="../../../math/Ray3.js"/>
/// <reference path="../../../drawing/Face.js"/>
/// <reference path="../../../drawing/Line.js" />
/// <reference path="../../../drawing/Light.js" />
/// <reference path="Viewport.js" />
/// <reference path="DeviceTypes.js"/>
/// <reference path="Transform.js"/>
/// <reference path="../BoundingSphere.js"/>
/// <reference path="../BoundingFrustum.js"/>
/// <reference path="RenderFace.js"/>

/*****************************************************************************
Ajaxna library class definition. Inheriting from ajax.components.Component.
Author: krobinson 11/3/2008 7:05:31 PM.
Class:  ajax.xna.framework.graphics.GraphicsDevice
*****************************************************************************/

$addNamespace("ajax.xna.framework.graphics");
ajax.lang.imports("ajax.xna.framework.graphics.Transform");
ajax.lang.imports("ajax.drawing.RenderStateManager");
ajax.lang.imports("ajax.drawing.IGraphics");
ajax.lang.imports("ajax.xna.framework.graphics.Viewport");
ajax.lang.imports("ajax.xna.framework.BoundingFrustum");
ajax.lang.imports("ajax.xna.framework.BoundingSphere");
ajax.lang.imports("ajax.xna.framework.graphics.RenderFace");
ajax.lang.imports("ajax.xna.framework.graphics.DeviceTypes");

//-----------------------------------------------------------------------------

ajax.xna.framework.graphics.GraphicsDevice = function(doc, parentNode, w, h, deviceType, graphicsScale)
{///<summary>Creates a graphics device, which contains a default viewport attached to the parentNode.</summary>
    ///<param name="doc" type="domDocument">The document in which to use this GraphicsDevice.</param>
    ///<param name="parentNode" type="domElement">The parent node that the default Viewport will attach to.</param>
    ///<param name="w" type="number" integer="true">The width in pixels of the default viewport.</param>
    ///<param name="h" type="number" integer="true">The height in pixels of the default viewport.</param>
    ///<param name="deviceType" type="ajax.xna.framework.graphics.DeviceTypes" optional="true">(Optional) The type of graphics to use. Default is Canvas/VML</param>
    ///<param name="graphicsScale" type="float" optional="true">(Optional) Unsupported.</param>
    ///<field name="lights" type="Array" elementType="ajax.drawing.Light"/>
    ///<field name="deviceType" type="ajax.xna.framework.graphics.DeviceTypes"/>
    ///<field name="renderFaceBuffer" type="Array" elementType="ajax.math.Vector3"/>



    ajax.xna.framework.graphics.GraphicsDevice.superConstructor.call(this, doc);

    deviceType = ajax.isDefined(deviceType) ? deviceType : ajax.xna.framework.graphics.DeviceTypes.Canvas;
    graphicsScale = ajax.isDefined(graphicsScale) ? graphicsScale : 1;

    this._graphicsScale = graphicsScale;
    this.deviceType = deviceType;
    this.renderFaceBuffer = new Array();
    this.lights = new Array();
    this.transform = $new("ajax.xna.framework.graphics.Transform", this);
    this.facesSent = 0;
    this.renderState = $new("ajax.drawing.RenderStateManager");

    // Stuff populated after load
    this.viewport = $new("ajax.xna.framework.graphics.Viewport", doc, parentNode, "", 0, 0, w, h, graphicsScale);
    this.viewport = ajax.xna.framework.graphics.Viewport.cast(this.viewport);
    this.viewport.show();
    this.boundingFrustum = new ajax.xna.framework.BoundingFrustum(ajax.math.Matrix.identity());

    this.createGraphics(deviceType);

    // Events
    this.getEvent(this.getWindow(), "onDisposing");

}
$extend("ajax.xna.framework.graphics.GraphicsDevice", "ajax.components.Component");
if ($getDesignMode())
    ajax.xna.framework.graphics.GraphicsDevice.prototype = ajax.components.Component.prototype;
//-----------------------------------------------------------------------------

// Public members
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.lights = Array.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype._graphicsScale = 0;
ajax.xna.framework.graphics.GraphicsDevice.prototype.deviceType = ajax.xna.framework.graphics.DeviceTypes.Canvas;
ajax.xna.framework.graphics.GraphicsDevice.prototype.renderFaceBuffer = Array.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype.transform = ajax.xna.framework.graphics.Transform.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype.facesSent = 0;
ajax.xna.framework.graphics.GraphicsDevice.prototype.renderState = ajax.drawing.RenderStateManager.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype.viewport = ajax.xna.framework.graphics.Viewport.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype.graphics = ajax.drawing.IGraphics.prototype;
ajax.xna.framework.graphics.GraphicsDevice.prototype.boundingFrustum = ajax.xna.framework.BoundingFrustum.prototype;



// Public Methods
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.add_disposing = function(handler) { this.getEvent(this.getWindow(), "onDisposing").addListener(handler); }
ajax.xna.framework.graphics.GraphicsDevice.prototype.remove_disposing = function(handler) { this.getEvent(this.getWindow(), "onDisposing").removeListener(handler); }

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.cullBackFaces = function(renderFaces)
{///<summary>Culls (removes from the array) the back faces (facing away from the viewer) of the passed in renderFaces array based on our renderState CullMode.</summary>
    ///<param name="renderFaces" type="Array" elementType="ajax.xna.framework.graphics.RenderFace"/>    
    var rs = this.renderState;

    // Mesh culling can only be LESS culling than the renderstate. If the rs says none, then that's it.
    if (rs.cullMode == ajax.drawing.CullModes.None)
        return;

    // Loop through all faces and set the face to be invisible
    // if the new face normal is facing away from the origin
    var zeroVector = ajax.math.Vector3.zero();

    for (var i = 0; i < renderFaces.length; i++)
    {
        var face = ajax.xna.framework.graphics.RenderFace.cast(renderFaces[i]);

        // early skip - check renderface culling
        if (face.cullMode == ajax.drawing.CullModes.None)
            continue;

        // Dot the camera space Normal with a vector from us (origin) to a point on the triangle (camera space vert)                
        var cosAngle = face.normal.dot(face.cameraSpaceVertA);
        var sng = ajax.math.Vector3.sign(cosAngle);

        if (
            (rs.cullMode == ajax.drawing.CullModes.AntiClockwise && sng == 1)
            ||
            (rs.cullMode == ajax.drawing.CullModes.Clockwise && sng == -1)
            )
        {
            // this face is backfacing so remove it            
            renderFaces.removeAt(i--);
        }

    }
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.nearZPolyClip = function(nearZClip, face, renderFaces)
{///<summary>This method will clip the renderFace passed to the near Z viewing value of our current Viewport.</summary>
    ///<param name="face" type="ajax.drawing.RenderFace"/>
    ///<param name="renderFaces" type="Array" elementType="ajax.xna.framework.graphics.RenderFace"/>
    ///<returns type="boolean">// returns true if face is totally in front of near Z</returns>
    //face = ajax.xna.framework.graphics.RenderFace.cast(face);

    // Here we need to clip individual Polygons on a per vertex basis as they approach the camera
    // We will split one poly into 2 if we need to and add the extra one to the passed renderFaces buffer.
    // You see the last line of code of this method where we add the newly created face (if needed).

    var iNumVertsIn = 0;
    var bVA = false;
    var bVB = false;
    var bVC = false;

    // Check vert A
    if (face.cameraSpaceVertA.z < nearZClip)
    {
        iNumVertsIn++;
        bVA = true;
    }
    // Check vert B
    if (face.cameraSpaceVertB.z < nearZClip)
    {
        iNumVertsIn++;
        bVB = true;
    }
    // Check vert C
    if (face.cameraSpaceVertC.z < nearZClip)
    {
        iNumVertsIn++;
        bVC = true;
    }

    // If num verts In == 0 or 3 then we just return as its either TOTALLY in front or behind the near z boundary.
    if (iNumVertsIn == 0)
        return true; // Vert is in front of near z boundary.
    if (iNumVertsIn == 3)
        return false; // Vert is behind near Z boundary

    // Create temp storage as we need to clip this poly
    var tempVerts = new Array(3);
    if (iNumVertsIn == 1)
    {
        // 1 vert in frustum
        if (bVA)
        {
            // Use the copy constructor as these are REFERENCE type variables
            // So assigning one to the other will NOT copy the values across
            // to a new instance
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);
        }
        else if (bVB)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);
        }
        else if (bVC)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertA);
        }

        // Parametric line stuff
        // p = v0 + v01*t

        // First vert point...
        var v01 = ajax.math.Vector3.subtract(tempVerts[1], tempVerts[0]);
        var t1 = ((nearZClip - tempVerts[0].z) / v01.z);

        var newz1 = nearZClip;
        var newx1 = tempVerts[0].x + v01.x * t1;
        var newy1 = tempVerts[0].y + v01.y * t1;

        // Second Vert point...
        var v02 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[0]);
        var t2 = ((nearZClip - tempVerts[0].z) / v02.z);

        var newz2 = nearZClip;
        var newx2 = tempVerts[0].x + v02.x * t2;
        var newy2 = tempVerts[0].y + v02.y * t2;

        // Assign calculated vert1
        tempVerts[1].x = newx1;
        tempVerts[1].y = newy1;
        tempVerts[1].z = newz1;

        // Assign calculated vert2
        tempVerts[2].x = newx2;
        tempVerts[2].y = newy2;
        tempVerts[2].z = newz2;

        if (bVA)
        {
            face.cameraSpaceVertB = tempVerts[1];
            face.cameraSpaceVertC = tempVerts[2];
        }
        else if (bVB)
        {
            face.cameraSpaceVertA = tempVerts[1];
            face.cameraSpaceVertC = tempVerts[2];
        }
        else if (bVC)
        {
            face.cameraSpaceVertB = tempVerts[1];
            face.cameraSpaceVertA = tempVerts[2];
        }

        // Recompute the screen coords
        face.reComputeCameraToScreen(this.viewport);

    }
    else if (iNumVertsIn == 2)
    {
        // 2 Verts in frustum
        if (!bVA)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertA);
        }
        else if (!bVB)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertB);
        }
        else if (!bVC)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);
        }

        // Parametric line stuff
        // p = v0 + v01*t

        //Calculate first point
        var v01 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[0]);

        var t1 = ((nearZClip - tempVerts[0].z) / v01.z);

        var newz1 = nearZClip;
        var newx1 = tempVerts[0].x + v01.x * t1;
        var newy1 = tempVerts[0].y + v01.y * t1;

        // Calculate Second point
        var v02 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[1]);

        var t2 = ((nearZClip - tempVerts[1].z) / v02.z);

        var newz2 = nearZClip;
        var newx2 = tempVerts[1].x + v02.x * t2;
        var newy2 = tempVerts[1].y + v02.y * t2;

        // Assign first point
        tempVerts[2].x = newx1;
        tempVerts[2].y = newy1;
        tempVerts[2].z = newz1;

        if (!bVA)
            face.cameraSpaceVertA = tempVerts[2];
        else if (!bVB)
            face.cameraSpaceVertB = tempVerts[2];
        else if (!bVC)
            face.cameraSpaceVertC = tempVerts[2];

        // Recompute the screen coords
        face.reComputeCameraToScreen(this.viewport);

        // We now need to create a new face, based on !!
        // v1 = new1
        // v2 = new2
        // v3 = tempVerts[1]
        // and the existing face's normal and color        
        var newFace = ajax.xna.framework.graphics.RenderFace.create(
            tempVerts[2],
            new ajax.math.Vector3(newx2, newy2, newz2),
            tempVerts[1],
            face.color,
            face.normal,
            this.viewport,
            face.projectionMatrix
            );

        // Use the same zBias
        newFace.zBias = face.zBias;

        // Use the same culling and filling of the face we were sent
        newFace.cullMode = face.cullMode;
        newFace.fillMode = face.fillMode;
        newFace.lighting = face.lighting;
        newFace.lights = face.lights;


        // Add the new face we created dynamically
        renderFaces[renderFaces.length] = newFace;
    }
    return false;
}
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.cullFacesToFrustum = function(renderFaces)
{///<summary>Culls faces to current viewing Frustum</summary>
    ///<param name="renderFaces" type="Array" elementType="ajax.xna.framework.graphics.RenderFace"/>

    // Early out
    if (this.renderState.faceFrustumCullMode == ajax.drawing.FrustumCullModes.None)
        return;

    // Cache this value (slightly faster).
    var zNear = -this.viewport.get_minDepth();
    // And only check the number of faces we have right now - we may split some (making 2 out of 1). 
    // If we do then both will be SAFE so no need to check new ones we add.
    var numFaces = renderFaces.length;

    for (var i = 0; i < numFaces; i++)
    {
        var face = ajax.xna.framework.graphics.RenderFace.cast(renderFaces[i]);

        // Split any faces that cross the near boundary.
        if (this.renderState.nearZPolyClipEnabled)
        {
            if (this.nearZPolyClip(zNear, face, renderFaces)) // returns true if face is totally in front of near Z boundary (so should be removed).
            {
                renderFaces.removeAt(i--);
                numFaces--;
                continue;
            }
        }

        if (this.renderState.faceFrustumCullMode != ajax.drawing.FrustumCullModes.XYZ)
            continue;

        // Ok so XYZ selected...
        // Clip it if none of the vertices for the face is within the viewing frustum? This is not actually correct...so..
        // TODO: We need to create the edges from the camera Transformed verts
        // And test to see if any of the edges of this face intersect with the bounding frustum.
        if (
            this.boundingFrustum.containsVector3(face.cameraSpaceVertA) == ajax.xna.framework.ContainmentType.Disjoint &&
            this.boundingFrustum.containsVector3(face.cameraSpaceVertB) == ajax.xna.framework.ContainmentType.Disjoint &&
            this.boundingFrustum.containsVector3(face.cameraSpaceVertC) == ajax.xna.framework.ContainmentType.Disjoint

        )
        {
            // OK, before we clip it, should we create a bounding sphere out of the renderface (cameraTransformed)
            // vertices and check THAT against the frustum - just in case it's very large or very close and 
            // 

            // None of the verts for this face in frustum...cull it"
            renderFaces.removeAt(i--);
            numFaces--;
            continue;

        }
    }
}


//-----------------------------------------------------------------------------

ajax.xna.framework.graphics.GraphicsDevice.prototype._appendRenderFaces = function(faces, cameraSpaceVerts, cameraSpaceNormals, screenSpaceVerts, zBias)
{
    // Copy the current lights array as they currently are to apply it to all renderfaces
    var lightsCopy = this.lights.copy();

    // We need to Append these Renderfaces to our list so that we can ultimately draw them
    for (var i = 0; i < faces.length; i++)
    {
        // Create new RenderFace - use the current projection matrix.
        var newFace = new ajax.xna.framework.graphics.RenderFace(faces[i], cameraSpaceNormals[i], cameraSpaceVerts, screenSpaceVerts, this.transform.get_projection(), zBias)
        // Copy some of our current renderstate data to this face.				        
        newFace.cullMode = this.renderState.cullMode;
        newFace.fillMode = this.renderState.fillMode;
        newFace.lighting = this.renderState.lighting;
        newFace.lights = lightsCopy;
        this.renderFaceBuffer[this.renderFaceBuffer.length] = newFace;
    }
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.drawFaces = function(faces, vb, radius, zBias)
{///<summary>Called by meshes, send in the faces, vertex buffer and radius and this will
    ///  - Create a cameraSpace Matrix for this object (world * view)
    ///  - Do Object culling (is the object even in the view frustum?)
    ///  - Transforms all verts (from vertex buffer)
    ///  - Transforms all face normals
    ///  - Computes screen Verts
    ///  - Calls appendRenderFaces, creating RenderFaces for each mesh face.</summary>
    /// <param name="face" type="Array" elementType="ajax.drawing.Face">An array of Faces to draw.</param>
    /// <param name="vb" type="Array" elementType="ajax.math.Vector3">An array of vertices making the Vertex Buffer.</param>
    /// <param name="radius" type="float">The radius of the passed collection of vertices.</param>
    /// <param name="zBias" type="float">The ZBias to apply when rendering this geometry.</param>

    var Matrix = ajax.math.Matrix;

    // increment the faces were have been sent
    this.facesSent += faces.length;

    // Set the Camera Space Matrix = World * View
    var cameraSpaceMatrix = Matrix.multiply(this.transform.get_world(), this.transform.get_view());

    // If Object / Frustum culling is enabled, do that here. If the object is off-screen, no need to do much..
    if (this.renderState.objectFrustumCullMode != ajax.drawing.FrustumCullModes.None)
    {
        // In case there has been a scaling matrix applied, lets create a vector with length = radius
        var scaling = cameraSpaceMatrix.getScaleVector();

        // TODO: Deal with scaling of the radius here...for now just don't.        

        // Now transform it and get the new length back - a scaling matrix would have scaled it        
        var scaledRadius = radius;
        // Transform a zero vector to get the center of the object
        var center = cameraSpaceMatrix.transformVertex(new ajax.math.Vector3());
        // now create a bounding sphere to test against our frustum
        var sphere = new ajax.xna.framework.BoundingSphere(center, scaledRadius);

        // Big Early out to cull objects whose bounding sphere does not appear  
        if (!this.boundingFrustum.sphereIsInside(sphere))
            return;

    }
    // Object is somewhat inside the frustum so calculate screenSpaceMatrix as well (World * View * Projection).
    var screenSpaceMatrix = Matrix.multiply(cameraSpaceMatrix, this.transform.get_projection());


    // With most of the wastage out the way, we compute 
    // - The camera verts (World * View)
    // - The Camera Normals (World * View)
    // - The Screen Coordintates (World * View * Projection)

    // It's required that we do this as camera space verts are used for backface culling and Face/Frustum culling.
    // WE *COULD* work around this by transforming the camera to camera space and using the Object in Model space
    // *AND* Transforming our Frustum to Screen space (W*V*P) for each call to draw.
    var cameraVerts = cameraSpaceMatrix.transformVertices(vb);

    // Get a new Normals buffer with the Transformed Face Normals (these are used in backface removal (optimal) 
    // as well as lighting - so they ARE important.
    var cameraNormals = cameraSpaceMatrix.transformFaceNormals(faces);

    // TODO: isn't this a bit wasteful now? Don't you only need to compute the actual screenVerts if
    // we are definately going to render this (it could now be clipped).
    // Transform World to Screen	
    //var screenVerts = this.viewport.projectTransformedVertexBuffer(vb, cameraVerts, cameraSpaceMatrix); 
    var screenVerts = this.viewport.projectArray(vb, screenSpaceMatrix);

    // With all this computed, we add these faces to the renderFacebuffer
    this._appendRenderFaces(faces, cameraVerts, cameraNormals, screenVerts, zBias);
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.drawPolygon = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    // there must be at least 3 lines to make a polygon
    if (verts.length < 3)
        ajax.debug.Output.writeLn("DrawPolygon must have at least 3 verts");

    var g = this.graphics;
    g.drawPolygon3(verts[0].x, verts[0].y, verts[1].x, verts[1].y, verts[2].x, verts[2].y);
}
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.fillPolygon = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    // there must be at least 3 lines to make a polygon
    if (verts.length < 3)
        ajax.debug.Output.writeLn("FillPolygon must have at least 3 verts");

    this.graphics.fillPolygon3(
        verts[0].x,
        verts[0].y,
        verts[1].x,
        verts[1].y,
        verts[2].x,
        verts[2].y
     );
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.drawPoints = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    var g = this.graphics;

    for (var i = 0; i < verts.length; i++)
        g.fillRect(verts[i].x, verts[i].y, 4, 4);
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.drawRenderFaces = function()
{
    var g = this.graphics;

    // Unroll this to optimise.
    // Draw each face we've been sent
    for (var i = 0; i < this.renderFaceBuffer.length; i++)
    {
        var face = ajax.xna.framework.graphics.RenderFace.cast(this.renderFaceBuffer[i]);

        // Work out what color to draw this (lighting would be done here)
        var drawColor = face.computeColor(this);
        g.setColor(drawColor);  // set to graphics				                

        // Always use the renderface fillmode - the renderstate version is used as a default only.
        // switch(this.renderState.fillMode)
        switch (face.fillMode)
        {
            case ajax.drawing.FillModes.Solid:
                this.fillPolygon(face.screenSpaceVerts);
                break;
            case ajax.drawing.FillModes.Wireframe:
                this.drawPolygon(face.screenSpaceVerts);
                break;
            case ajax.drawing.FillModes.Point:
                this.drawPoints(face.screenSpaceVerts);
                break;
        }
    }
}
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.createGraphics = function(deviceType)
{///<param name="deviceType" type="ajax.xna.framework.graphics.DeviceTypes">The type of graphics to create.</param>    
    // Default graphics type
    var graphicsClassName = "ajax.drawing.GraphicsCanvas";

    switch (deviceType)
    {
        case ajax.xna.framework.graphics.DeviceTypes.Canvas:
            graphicsType = "ajax.drawing.GraphicsCanvas";
            break;
        case ajax.xna.framework.graphics.DeviceTypes.Native:
            graphicsType = "ajax.drawing.Graphics";
            break;
    }

    // Create graphics for the current viewport
    this.graphics = $new(graphicsClassName, this.getDocument(), this.viewport.getLayer(), this.viewport.width, this.viewport.height);
    this.graphics = ajax.drawing.IGraphics.cast(this.graphics);
    this.graphics.setScale(this._graphicsScale);

    // Set the scale of the graphics to the viewport.
    this.viewport.set_scale(this.graphics.getScale());
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.setRenderTarget = function(renderTargetIndex, domElement)
{///<summary>Enables rendering to another DOM element. Not Implemented yet.</summary>
    //TODO: implement rendering to another DOM element. Could be done with Viewport...?
}





//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.clear = function(colour)
{///<param name="colour" type="ajax.drawing.Colour"/>    
    this.graphics.clear(colour);
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.beginDraw = function()
{///<summary>Sets the graphics device to begin rendering the next scene (frame). 
    /// Call this at the start of your drawing loop, then call endScene() at the end.</summary>
    ///<returns type="boolean">Returns true if all is well.</returns>
    // Reset FacesSent
    this.facesSent = 0;

    // Reset RenderFaceBuffer
    this.renderFaceBuffer.clear(); // new Array();

    return true;
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice._compareRenderFaces = function(a, b)
{///<summary>Compares two RenderFaces.</summary>
    if (a.averageZ < b.averageZ)
        return -1;
    return 1;
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.endDraw = function()
{///<summary>Call this when you are finished drawing a frame.
    /// It signals the end of 3D drawing for this frame. 
    /// Culling is performed, RenderFaces are sorted and then drawn using the graphics adapter.
    /// call present() to complete the frame.
    ///</summary>

    // Cull Backfaces
    this.cullBackFaces(this.renderFaceBuffer);

    // Cull to frustum    
    this.cullFacesToFrustum(this.renderFaceBuffer);

    var rs = this.renderState;

    // Compute face z values and sort.
    if (rs.zBufferEnabled)
    {

        // Compute the average z for all Renderfaces in our collection             
        for (var i = 0; i < this.renderFaceBuffer.length; i++)
        {
            var face = this.renderFaceBuffer[i];
            face = ajax.xna.framework.graphics.RenderFace.cast(face);
            face.computeAverageZ();
        }
        // now SORT the render face buffer
        this.renderFaceBuffer.sort(ajax.xna.framework.graphics.GraphicsDevice._compareRenderFaces);
    }

    // Clients can turn off rendering to see the cost of all computations without the actual drawing.
    if (this.renderState.renderingEnabled)
        this.drawRenderFaces();
}
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.present = function()
{///<summary>Presents the backbuffer to the screen. Most HTML graphics adapters to not require this to be called, but some might. Call this after endScene().</summary>
    //this.graphics.context.stroke();
    // this is where we flip the Back & Front buffer        
    this.graphics.repaint();
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.dispose = function()
{
    // Fire disposing event.
    this.onDisposing.call(this);
    this.viewport.dispose();
    ajax.xna.framework.graphics.GraphicsDevice.superClass.dispose.call(this);
}

//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.prototype.getFaceInfo = function()
{
    var s = "F Sent: " + this.facesSent + ", F Rendered: " + this.renderFaceBuffer.length;
    return s;
}


// Statics
//-----------------------------------------------------------------------------
ajax.xna.framework.graphics.GraphicsDevice.cast = function(obj)
{///<returns type="ajax.xna.framework.graphics.GraphicsDevice"/>
    return obj;
}